Generative Adversarial Networks (GAN)

L'objectif de ce TP est de créer et manipuler un Generative Adversarial Networks (GAN) afin de générer des images de chiffres manuscrits à partir d'une base de données d'exemples.

Vous aurez besoin des outils présents dans le fichier gan_tools_cuda.py que vous devrez copier dans votre répertoire Jupyter afin de pouvoir l'importer. Regardez bien les fonctions et classes définies dans ce fichier : il y a les codes permettant de télécharger la base de donnée d'images, de faire les conversions entre images et vecteurs, de générer les vecteurs de bruit à donner en entrée du générateur et de gérer l’affichage des résultats sous forme d'exemples d’images générées par le générateur et des indices de qualité du générateur et du discriminateur.

1. Importation des librairies et de gan_tools_cuda.py
2. Chargement des données d'apprentissage et création du "loader"

Le loader présentera au réseau les données par paquet de 100, dans un ordre aléatoire.

3. Création du discriminateur

Créez une classe Python DiscriminatorNet décrivant la structure du réseau discriminateur.

Le réseau est composé de trois couches cachées et d'une couche de sortie. Nous utiliserons le module "séquentiel" de Pytorch qui décrit l’ordre des différentes transformations à appliquer aux données dans chaque couche.

L'entrée du réseau est un vecteur avec 784 valeurs (une image 28x28), la sortie est une valeur unique, allant de 0 (fausse image) à 1 (vraie image) grâce à l'utilisation d'une fonction sigmoïde.

Les trois couches cachées utilisent une fonction LeakyReLU pour transformer leur sortie en valeurs quasi positives, avec alpha = 0,2. Elles utilisent également une fonction Dropout pour définir aléatoirement à zéro 30% des valeurs de sortie des neurones des couches cachées (il a été prouvé que cela augmentait les performances du réseau en empêchant le sur-apprentissage). Le nombre de neurones dans chaque couche cachée est respectivement de 1024, 512 et 256.

La classe possède une méthode "forward" permettant de calculer la sortie du réseau à partir d'un vecteur d'entrée (représenaant une image).

4. Création du générateur

Créez une classe Python GeneratorNet décrivant la structure du réseau générateurs. Le réseau est composé de trois couches cachées et d'une couche de sortie. L'entrée du réseau est un vecteur avec 100 valeurs, la sortie est une image avec 784 valeurs (utilisez nn.Tanh() au lieu de nn.Sigmoid() pour convertir les valeurs de sortie). Les trois couches cachées utilisent une fonction LeakyReLU pour transformer leur sortie en valeurs quasi positives, avec alpha = 0,2, mais pas de Dropout ici. Le nombre de neurones dans chaque couche cachée est respectivement de 256, 512 et 1024

5. Creation du processus d'apprentissage

Initialisez les instances d'un discriminateur et d'un générateurs. Créez deux optimiseurs de type Adam pour les deux réseaux. Définissez la fonction de perte: ici, nous choisissons une fonction d'entropie croisée binaire pour vérifier si le discriminateur trouve la bonne réponse.

Créez une fonction train_generator et une fonction train_discriminator. Expliquez les différentes étapes du processus d'apprentissage.

D'après le cours, si nous remplaçons vᵢ = D(xᵢ) et yᵢ=1 (pour tout i) dans la définition de la perte BCE, nous obtenons la perte liée aux images réelles. A l'inverse, si on fixe vᵢ = D(G(zᵢ)) et yᵢ=0 pour tout i, on obtient la perte liée aux images fake.

Dans le modèle mathématique d'un GAN , le gradient de celle-ci devait être ascendant, mais PyTorch et la plupart des autres frameworks de Machine Learning minimisent généralement les fonctions à la place. Puisque la maximisation d'une fonction est équivalente à la minimisation de son négatif, et que le terme BCE-Loss a un signe moins, nous n'avons pas besoin de nous soucier du signe.

De plus, nous pouvons observer que les cibles des images réelles sont toujours des 1, tandis que les cibles des images fausses sont des 0. On utilise les fonctions reel_data_target et fake_data_target définies dans le fichier gan_tools.

En additionnant ces deux pertes du discriminateur, nous obtenons la perte totale du mini-batch pour le discriminateur.

Nous calculerons les gradients séparément, puis nous les mettrons à jour ensemble.

Plutôt que de minimiser log(1- D(G(z)), l'entraînement du générateur pour maximiser log D(G(z)) fournira des gradients beaucoup plus forts au début de l'entraînement. Les deux pertes peuvent être échangées de manière interchangeable puisqu'elles entraînent la même dynamique pour le Générateur et le Discriminateur. La maximisation de log D(G(z)) est équivalente à la minimisation de sa valeur négative et, comme la définition de la perte BCE-Loss comporte un signe moins, il n'est pas nécessaire de tenir compte du signe. De même que pour le Discriminateur, si nous fixons vᵢ = D(G(zᵢ)) et yᵢ=1 ∀ i, nous obtenons la perte souhaitée à minimiser.

6. Apprentissage et visualisation:

Pour chaque itération et pour chaque lot ("batch") de données, les étapes d'apprentissage sont les suivantes:

Attention :

Il se peut que le système se "bloque" dans une configuration où la sortie du générateur est la même quelle que soit l'entrée. Si toutes les images des exemples de sortie sont identiques après une dizaine d'itérations et que D(x) = 1 et D(G(z)) = 0, il faut relancer l'apprentissage (redémarrer le noyau).

Au début, les images générées sont du pur bruit, mais elles s'améliorent ensuite, jusqu'à ce qu'on obtienne des images assez bonnes.

Il est également possible de visualiser le processus d'apprentissage. Comme vous pouvez le voir dans le bloc précédent, l'erreur du discriminateur est très élevée au début, car il ne sait pas comment classer correctement les images comme étant réelles ou fausses. Au fur et à mesure que le discriminateur s'améliore et que son erreur diminue à environ 0,4 . Quant à l'erreur du générateur, elle augmente, ce qui prouve que le discriminateur est plus performant que le générateur et qu'il peut classer correctement les échantillons faux.

En poursuivant l'entraînement, l'erreur du générateur diminue, ce qui implique que les images qu'il génère sont de mieux en mieux. Alors que le générateur s'améliore, l'erreur du discriminateur augmente, car les images deviennent chaque fois plus réalistes.

7. Exploration et optimisation

Modifiez la structure des deux réseaux (discriminateur et générateur) en ajoutant ou enlevant des couches cachées, en modifiant le nombre de neurones dans ces couches et en jouant sur les paramètres des fonctions LeakyReLU et Dropout.

Après avoir expliqué comment évaluer la qualité des images générées par le générateur, comparez la qualité des résultats obtenus avec les modifications que vous avez testé et essayez d’obtenir le meilleur résultat possible.

L'entraînement des modèles GAN demande beaucoup de patience en raison de sa profondeur. Nous avons décidé de resumer ce que l'on a appris à travers la modification des paramètres sous forme de conseils: